﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace MemoDb
{
    public class Memo
    {
        private readonly IDictionary<Type, TypeStorage> storages = new Dictionary<Type, TypeStorage>();
        public AssignIdMode AssignIdMode { get; private set; }
        public bool InsertByCascadeByDefault { get; private set; }
        public bool DeleteOrphansByCascadeByDefault { get; private set; }

        public Memo(AssignIdMode assignIdMode = AssignIdMode.OnInsert, bool insertByCascadeByDefault = false, bool deleteOrphansByCascadeByDefault = false)
        {
            AssignIdMode = assignIdMode;
            InsertByCascadeByDefault = insertByCascadeByDefault;
            DeleteOrphansByCascadeByDefault = deleteOrphansByCascadeByDefault;
        }

        public Memo Map<T>(Action<ITypeConfigurator<T>> configure = null)
        {
            var configurator = new TypeConfigurator<T>();
            if (configure != null)
                configure(configurator);

            storages.Add(typeof(T), configurator.BuildTypeStorage());
            return this;
        }

        public Memo Map(Type type, Action<ITypeConfigurator> configure = null)
        {
            var configurator = new TypeConfigurator(type);
            if (configure != null)
                configure(configurator);

            storages.Add(type, configurator.BuildTypeStorage());
            return this;
        }

        public MemoSession CreateSession()
        {
            return new MemoSession(storages, this);
        }

        private class TypeConfigurator<T> : ITypeConfigurator<T>
        {
            private IdMapper id;
            private readonly List<Cascade> cascadeInserts = new List<Cascade>();
            private readonly List<Cascade> cascadeDeleteOrphans = new List<Cascade>();

            public ITypeConfigurator<T> SetId<TId>(PropertyInfo id, Func<TId, TId> getNewId, TId unsavedValue)
            {
                return SetId(new IdMapper<TId>(id, getNewId, unsavedValue));
            }

            public ITypeConfigurator<T> CasacadeInsert(string reference, bool cascade)
            {
                return CasacadeInsert(typeof(T).GetProperty(reference), cascade);
            }

            public ITypeConfigurator<T> CasacadeInsert(PropertyInfo reference, bool cascade)
            {
                cascadeInserts.Add(new Cascade(reference, cascade));
                return this;
            }

            public ITypeConfigurator<T> CasacadeInsert(Expression<Func<T, object>> reference, bool cascade)
            {
                return CasacadeInsert(reference.ToPropertyInfo(), cascade);
            }

            public ITypeConfigurator<T> CasacadeDeleteOrphans(string collection, bool cascade)
            {
                return CasacadeDeleteOrphans(typeof (T).GetProperty(collection), cascade);
            }

            public ITypeConfigurator<T> CasacadeDeleteOrphans(PropertyInfo collection, bool cascade)
            {
                cascadeDeleteOrphans.Add(new Cascade(collection, cascade));
                return this;
            }

            public ITypeConfigurator<T> CasacadeDeleteOrphans(Expression<Func<T, IEnumerable>> collection, bool cascade)
            {
                return CasacadeDeleteOrphans(collection.ToPropertyInfo(), cascade);
            }

            public ITypeConfigurator<T> SetId<TId>(string id, Func<TId, TId> getNewId, TId unsavedValue)
            {
                return SetId(typeof(T).GetProperty(id), getNewId, unsavedValue);
            }

            public ITypeConfigurator<T> SetId(PropertyInfo id, bool autoAssign, object unsavedValue)
            {
                return SetId(autoAssign ? IdMapper.AutoAssign(id, unsavedValue) : IdMapper.Assigned(id, unsavedValue));
            }

            private ITypeConfigurator<T> SetId(IdMapper idMapper)
            {
                id = idMapper;
                return this;
            }

            public ITypeConfigurator<T> SetId<TId>(Expression<Func<T, TId>> id, Func<TId, TId> getNewId, TId unsavedValue) 
            {
                return SetId(id.ToPropertyInfo(), getNewId,unsavedValue);
            }

            public ITypeConfigurator<T> SetId(string id, bool autoAssign, object unsavedValue)
            {
                return SetId(typeof(T).GetProperty(id), autoAssign, unsavedValue);
            }

            public TypeStorage BuildTypeStorage()
            {
                return new TypeStorage(typeof(T), id, cascadeInserts, cascadeDeleteOrphans);
            }
        }

        private class TypeConfigurator : ITypeConfigurator
        {
            private IdMapper id;
            private readonly List<Cascade> cascadeInserts = new List<Cascade>();
            private readonly Type type;
            private readonly List<Cascade> cascadeDeleteOrphans = new List<Cascade>();

            public TypeConfigurator(Type type)
            {
                this.type = type;
            }


            public ITypeConfigurator CasacadeDeleteOrphans(string collection, bool cascade)
            {
                return CasacadeDeleteOrphans(type.GetProperty(collection), cascade);
            }

            public ITypeConfigurator CasacadeDeleteOrphans(PropertyInfo collection, bool cascade)
            {
                cascadeDeleteOrphans.Add(new Cascade(collection, cascade));
                return this;
            }

            public ITypeConfigurator SetId<TId>(PropertyInfo id, Func<TId, TId> getNewId, TId unsavedValue)
            {
                return SetId(new IdMapper<TId>(id, getNewId, unsavedValue));
            }

            public ITypeConfigurator SetId<TId>(string id, Func<TId, TId> getNewId, TId unsavedValue)
            {
                return SetId(type.GetProperty(id), getNewId, unsavedValue);
            }

            private ITypeConfigurator SetId(IdMapper idMapper)
            {
                id = idMapper;
                return this;
            }

            public ITypeConfigurator SetId(PropertyInfo id, bool autoAssign, object unsavedValue)
            {
                return SetId(autoAssign ? IdMapper.AutoAssign(id,unsavedValue) : IdMapper.Assigned(id,unsavedValue));
            }

            public ITypeConfigurator SetId(string id, bool autoAssign, object unsavedValue)
            {
                return SetId(type.GetProperty(id), autoAssign,unsavedValue);
            }

            public ITypeConfigurator CasacadeInsert(string reference, bool cascade)
            {
                return CasacadeInsert(type.GetProperty(reference), cascade);
            }
            
            public ITypeConfigurator CasacadeInsert(PropertyInfo reference,bool cascade)
            {
                cascadeInserts.Add(new Cascade(reference, cascade));
                return this;
            }

            public TypeStorage BuildTypeStorage()
            {
                return new TypeStorage(type, id, cascadeInserts, cascadeDeleteOrphans);
            }
        }

        public bool CascadeInsert(PropertyInfo property)
        {
            var cascade = storages[property.ReflectedType].CascadeInsert(property);
            return cascade == null ? InsertByCascadeByDefault : cascade.Enabled;
        }

        public bool CascadeDeleteOrphans(PropertyInfo property)
        {
            var cascade = storages[property.ReflectedType].CascadeDeleteOrphans(property);
            return cascade == null ? DeleteOrphansByCascadeByDefault : cascade.Enabled;
        }

        public IDictionary<Type, ITypeMapping> Mappings
        {
            get { return storages.ToDictionary(x => x.Key, x => (ITypeMapping)x.Value); }
        }
    }
}